﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace FCSChart.Graphical
{
    /// <summary>
    /// 4象限门
    /// </summary>
    public class QuadrantGraphical : BaseGraphical
    {
        private double[] points;
        /// <summary>
        /// 4象限门5个点位
        /// </summary>
        public double[] Points
        {
            get { return points; }
            protected set { points = value; OnPropertyChanged("Points"); }
        }
        public QuadrantGraphical()
        {
            Points = new double[6];
            this.Fill = Brushes.Transparent;
        }
        public QuadrantGraphical(QuadrantGraphicalModel model) : base(model)
        {
            this.Points = new double[] { model.Left, model.Top, model.Right, model.Bottom, model.CenterX, model.CenterY };
            this.Fill = Brushes.Transparent;
            if (model.AreaNames != null && model.AreaNames.Length == 4)
            {
                Areas = model.AreaNames.Select(p => new GraphicalArea() { Name = p, OwnerGraphical = this }).ToArray();
                Helper.AddExistedGraphicalName(this.ShortName, model.AreaNames.ToArray());
                if (model.AreaColors != null && model.AreaColors.Length == 4)
                {
                    Areas[0].DisplayColor = model.AreaColors[0];
                    Areas[1].DisplayColor = model.AreaColors[1];
                    Areas[2].DisplayColor = model.AreaColors[2];
                    Areas[3].DisplayColor = model.AreaColors[3];
                }
            }
        }
        protected override void InitName()
        {
            this.Name = "Quadrant";
            this.ShortName = "Q";
        }

        #region 4象限门不移动
        internal override void Move(double x, double y) { }
        #endregion

        #region 绘制
        internal override void Drawing()
        {
            if (OwnerChart == null || !OwnerChart.IsLoaded
                || OwnerChart.XAxis == null || !OwnerChart.XAxis.IsLoaded || OwnerChart.XAxis.ActualWidth == 0
                || OwnerChart.YAxis == null || !OwnerChart.YAxis.IsLoaded || OwnerChart.YAxis.ActualHeight == 0) return;
            if (GraphicalShape == null)
            {
                Binding binding = new Binding("Points") { Source = this, Converter = Converters.QuadrantDoublesToGeometryConverter.Converter, ConverterParameter = OwnerChart, Mode = BindingMode.OneWay };
                var temp = new Path() { Cursor = Cursors.Hand };
                temp.SetBinding(Path.DataProperty, binding);
                temp.SetBinding(Shape.FillProperty, new Binding("Fill") { Source = this });
                temp.SetBinding(Shape.StrokeProperty, new Binding("Stroke") { Source = this });
                temp.SetBinding(Shape.StrokeThicknessProperty, new Binding("StrokeThickness") { Source = this });
                temp.SetBinding(FrameworkElement.ContextMenuProperty, new Binding("ContextMenu") { Source = this });
                temp.SetBinding(UIElement.FocusableProperty, new Binding("CanChangeGraphical") { Source = this.OwnerChart });
                temp.MouseDown += Graphical_MouseDown;
                GraphicalShape = temp;
            }
            OnPropertyChanged("Points");
            if (!IsCreateing)
            {
                DrawingControl();
            }
        }

        internal override void PanelMouseDown(object sender, MouseButtonEventArgs e) { }

        internal override void PanelMouseMove(object sender, MouseEventArgs e)
        {
            if (IsCreateing && sender is Panel panel && Points.Length == 6)
            {
                var point = e.GetPosition(panel);
                var x = OwnerChart.XAxis.GetLocationValue(point.X);
                var y = OwnerChart.YAxis.GetLocationValue(point.Y);
                Points = new double[] { y, x, y, x, x, y };
            }
            else if (!IsCreateing && ControledShape != null)
            {
                Shape_MouseMove(ControledShape, e);
            }
        }

        internal override void PanelMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (IsCreateing)
            {
                IsCreateing = false;
            }
            else if (ControledShape != null)
            {
                Shape_MouseLeftButtonUp(ControledShape, e);
                ControledShape = null;
            }
        }
        #endregion

        #region 绘制门的控制按钮和区域
        protected override void DrawingControl()
        {
            var center = new Point(OwnerChart.XAxis.GetValueLocation(points[4]), OwnerChart.YAxis.GetValueLocation(points[5]));
            var left = new Point(5, OwnerChart.YAxis.GetValueLocation(points[0]));
            var top = new Point(OwnerChart.XAxis.GetValueLocation(points[1]), 5);
            var right = new Point(OwnerChart.ViewPanel.ActualWidth - 5, OwnerChart.YAxis.GetValueLocation(points[2]));
            var bottom = new Point(OwnerChart.XAxis.GetValueLocation(points[3]), OwnerChart.ViewPanel.ActualHeight - 5);
            if (Areas == null)
            {
                Areas = new GraphicalArea[] {
                    new GraphicalArea() { Name = OwnerChart.CreateNewAreaNameFunction(this), OwnerGraphical = this },
                    new GraphicalArea() { Name = OwnerChart.CreateNewAreaNameFunction(this), OwnerGraphical = this },
                    new GraphicalArea() { Name = OwnerChart.CreateNewAreaNameFunction(this), OwnerGraphical = this },
                    new GraphicalArea() { Name = OwnerChart.CreateNewAreaNameFunction(this), OwnerGraphical = this }
                };
            }
            this.OwnerChart.AddGraphicalArea(Areas[0], Areas[1], Areas[2], Areas[3]);
            if (ControlShapes.Count <= 0)
            {
                ControlShapes.Add(new Path() { Data = new EllipseGeometry(left, 5, 5), Cursor = Cursors.SizeNS });
                ControlShapes.Add(new Path() { Data = new EllipseGeometry(top, 5, 5), Cursor = Cursors.SizeWE });
                ControlShapes.Add(new Path() { Data = new EllipseGeometry(right, 5, 5), Cursor = Cursors.SizeNS });
                ControlShapes.Add(new Path() { Data = new EllipseGeometry(bottom, 5, 5), Cursor = Cursors.SizeWE });
                ControlShapes.Add(new Path() { Data = new EllipseGeometry(center, 5, 5), Cursor = Cursors.SizeAll });
            }
            else
            {
                var tempcenter = center;
                if (ControlShapes[4] is Path p4 && p4.Data is EllipseGeometry e4)
                {
                    tempcenter = e4.Center;
                    e4.Center = center;
                }
                if (ControlShapes[0] is Path p0 && p0.Data is EllipseGeometry e0)
                {
                    var templeft = e0.Center;
                    left.Y = (templeft.Y - tempcenter.Y) * (left.X - center.X) / (templeft.X - tempcenter.X) + center.Y;
                    Points[0] = OwnerChart.YAxis.GetLocationValue(left.Y);
                    e0.Center = left;
                }
                if (ControlShapes[1] is Path p1 && p1.Data is EllipseGeometry e1)
                {
                    var temptop = e1.Center;
                    top.X = (temptop.X - tempcenter.X) * (top.Y - center.Y) / (temptop.Y - tempcenter.Y) + center.X;
                    Points[1] = OwnerChart.XAxis.GetLocationValue(top.X);
                    e1.Center = top;
                }
                if (ControlShapes[2] is Path p2 && p2.Data is EllipseGeometry e2)
                {
                    var tempright = e2.Center;
                    right.Y = (tempright.Y - tempcenter.Y) * (right.X - center.X) / (tempright.X - tempcenter.X) + center.Y;
                    Points[2] = OwnerChart.YAxis.GetLocationValue(right.Y);
                    e2.Center = right;
                }
                if (ControlShapes[3] is Path p3 && p3.Data is EllipseGeometry e3)
                {
                    var tempbottom = e3.Center;
                    bottom.X = (tempbottom.X - tempcenter.X) * (bottom.Y - center.Y) / (tempbottom.Y - tempcenter.Y) + center.X;
                    Points[3] = OwnerChart.XAxis.GetLocationValue(bottom.X);
                    e3.Center = bottom;
                }
                OnPropertyChanged("Points");
            }
            Areas[0].Center = new Point(center.X / 2, center.Y / 2);
            Areas[1].Center = new Point((right.X + center.X) / 2, center.Y / 2);
            Areas[2].Center = new Point(center.X / 2, (bottom.Y + center.Y) / 2);
            Areas[3].Center = new Point((right.X + center.X) / 2, (bottom.Y + center.Y) / 2);
        }
        /// <summary>
        /// 移动门的控制按钮
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void Shape_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.OwnerChart.CanChangeGraphical && e.LeftButton == MouseButtonState.Pressed && sender is Path path && path.Data is EllipseGeometry ellipse)
            {
                var point = e.GetPosition(OwnerChart.ViewPanel);
                var index = ControlShapes.IndexOf(path);
                var value = Converters.AxisValueToViewValueConverter.Converter.ConvertBack(point, null, OwnerChart, null);
                if (value is Point p)
                {
                    switch (index)
                    {
                        case 0:
                            Points[0] = p.Y;
                            break;
                        case 1:
                            Points[1] = p.X;
                            break;
                        case 2:
                            Points[2] = p.Y;
                            break;
                        case 3:
                            Points[3] = p.X;
                            break;
                        case 4://中心点移动，会移动整个图形
                            var beforcenter = new Point(OwnerChart.XAxis.GetValueLocation(Points[4]), OwnerChart.YAxis.GetValueLocation(Points[5]));
                            if (ControlShapes[0] is Path ptemp1 && ptemp1.Data is EllipseGeometry eg1)
                            {
                                var a = (eg1.Center.Y - beforcenter.Y) / beforcenter.X;
                                eg1.Center = new Point(5, point.X * a + point.Y);
                                Points[0] = OwnerChart.YAxis.GetLocationValue(eg1.Center.Y);
                            }
                            if (ControlShapes[1] is Path ptemp2 && ptemp2.Data is EllipseGeometry eg2)
                            {
                                var a = (eg2.Center.X - beforcenter.X) / beforcenter.Y;
                                eg2.Center = new Point(point.Y * a + point.X, 5);
                                Points[1] = OwnerChart.XAxis.GetLocationValue(eg2.Center.X);
                            }
                            if (ControlShapes[2] is Path ptemp3 && ptemp3.Data is EllipseGeometry eg3)
                            {
                                var a = (eg3.Center.Y - beforcenter.Y) / (OwnerChart.ViewPanel.ActualWidth - beforcenter.X);
                                eg3.Center = new Point(OwnerChart.ViewPanel.ActualWidth - 5, (OwnerChart.ViewPanel.ActualWidth - point.X) * a + point.Y);
                                Points[2] = OwnerChart.YAxis.GetLocationValue(eg3.Center.Y);
                            }
                            if (ControlShapes[3] is Path ptemp4 && ptemp4.Data is EllipseGeometry eg4)
                            {
                                var a = (eg4.Center.X - beforcenter.X) / (OwnerChart.ViewPanel.ActualHeight - beforcenter.Y);
                                eg4.Center = new Point((OwnerChart.ViewPanel.ActualHeight - point.Y) * a + point.X, OwnerChart.ViewPanel.ActualHeight - 5);
                                Points[3] = OwnerChart.XAxis.GetLocationValue(eg4.Center.X);
                            }
                            Points[4] = p.X;
                            Points[5] = p.Y;
                            break;
                        default:
                            break;
                    }
                    ellipse.Center = point;
                    OnPropertyChanged("Points");
                }
                e.Handled = true;
            }
        }
        protected override void Shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (this.OwnerChart.CanChangeGraphical) DrawingControl();
            base.Shape_MouseLeftButtonUp(sender, e);
        }
        #endregion

        /// <summary>
        /// 刷新区域内数据
        /// </summary>
        internal override async void RefreshAreaSource(Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (Areas == null || Areas.Length != 4) return;
            if (!isCreateing && Points != null && Points.Length == 6 && OwnerChart != null && OwnerChart.XSource != null && OwnerChart.YSource != null && OwnerChart.XAxis != null && OwnerChart.YAxis != null)
            {
                var xSource = OwnerChart.XSource;
                var ySource = OwnerChart.YSource;
                var count = Math.Min(xSource.Count, ySource.Count);
                var parentIndexs = OwnerChart.Indexs;
                var pointdoubles = Points;
                Func<double, double> XAxisFunc = OwnerChart.XAxis.ValueToAxisValue;
                Func<double, double> YAxisFunc = OwnerChart.YAxis.ValueToAxisValue;

                var center = new Point(XAxisFunc(points[4]), YAxisFunc(points[5]));
                var left = new Point(OwnerChart.XAxis.MinAxis, YAxisFunc(points[0]));
                var top = new Point(XAxisFunc(points[1]), OwnerChart.YAxis.MaxAxis);
                var right = new Point(OwnerChart.XAxis.MaxAxis, YAxisFunc(points[2]));
                var bottom = new Point(XAxisFunc(points[3]), OwnerChart.YAxis.MinAxis);

                var maxDegreeOfParallelism = OwnerChart.Series == null ? 4 : OwnerChart.Series.MaxDegreeOfParallelism;
                try
                {
                    IsRefreshingAreaSource = true;
                    if (CancelTokenSource != null)
                    {
                        CancelTokenSource.Cancel();
                        CancelTokenSource.Dispose();
                    }
                    CancelTokenSource = new CancellationTokenSource();
                    var array = await Task.Factory.StartNew((tkn) =>
                     {
                         if (tkn is CancellationToken token)
                         {
                             var a1 = left.Y - center.Y;
                             var b1 = center.X - left.X;
                             var c1 = left.X * center.Y - center.X * left.Y;
                             var a2 = top.Y - center.Y;
                             var b2 = center.X - top.X;
                             var c2 = top.X * center.Y - center.X * top.Y;
                             var a3 = right.Y - center.Y;
                             var b3 = center.X - right.X;
                             var c3 = right.X * center.Y - center.X * right.Y;
                             var a4 = bottom.Y - center.Y;
                             var b4 = center.X - bottom.X;
                             var c4 = bottom.X * center.Y - center.X * bottom.Y;
                             ConcurrentBag<int>[] indexsArray = new ConcurrentBag<int>[] { new ConcurrentBag<int>(), new ConcurrentBag<int>(), new ConcurrentBag<int>(), new ConcurrentBag<int>() };
                             if (parentIndexs == null)
                             {
                                 try
                                 {
                                     var result = Parallel.For(0, count, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                                     {
                                         if (loop.IsStopped) return;
                                         if (xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                                         else PointInArea(i, indexsArray, left.Y, right.Y, top.X, bottom.X, xSource, ySource, xValueConverter, yValueConverter, XAxisFunc, YAxisFunc, a1, b1, c1, a2, b2, c2, a3, b3, c3, a4, b4, c4);
                                     });
                                     if (!result.IsCompleted) return null;
                                 }
                                 catch (System.OperationCanceledException) { return null; }
                             }
                             else
                             {
                                 try
                                 {
                                     var result = Parallel.ForEach(parentIndexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                                     {
                                         if (loop.IsStopped) return;
                                         if (xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                                         else PointInArea(i, indexsArray, left.Y, right.Y, top.X, bottom.X, xSource, ySource, xValueConverter, yValueConverter, XAxisFunc, YAxisFunc, a1, b1, c1, a2, b2, c2, a3, b3, c3, a4, b4, c4);
                                     });
                                     if (!result.IsCompleted) return null;
                                 }
                                 catch (System.OperationCanceledException) { return null; }
                             }
                             return indexsArray;
                         }
                         else return null;
                     }, CancelTokenSource.Token, CancelTokenSource.Token);

                    if (Areas == null || Areas.Length != 4 || array == null) return;
                    if (CancelTokenSource != null)
                    {
                        CancelTokenSource.Dispose();
                        CancelTokenSource = null;
                    }
                    this.Areas[0].InsideIndexs = array[0].ToArray();
                    this.Areas[1].InsideIndexs = array[1].ToArray();
                    this.Areas[2].InsideIndexs = array[2].ToArray();
                    this.Areas[3].InsideIndexs = array[3].ToArray();
                    IsRefreshingAreaSource = false;
                }
                catch (TaskCanceledException) { }
            }
            else
            {
                foreach (var area in Areas)
                {
                    area.InsideIndexs = null;
                }
            }
        }

        private void PointInArea(int i, ConcurrentBag<int>[] indexsArray, double leftY, double rightY, double topX, double bottomX, IList xSource, IList ySource, Func<object, double> xValueConverter, Func<object, double> yValueConverter, Func<double, double> xAxisFunc, Func<double, double> yAxisFunc, double a1, double b1, double c1, double a2, double b2, double c2, double a3, double b3, double c3, double a4, double b4, double c4)
        {
            var x = xAxisFunc(xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]));
            var y = yAxisFunc(yValueConverter == null ? Convert.ToDouble(ySource[i]) : yValueConverter(ySource[i]));
            var d1 = a1 * x + b1 * y + c1;
            if (d1 == 0) indexsArray[0].Add(i);//在中心点到左侧点的线上，算到第一象限里面
            else if (d1 > 0)//在中心点到左侧点的线的右侧
            {
                var d2 = a2 * x + b2 * y + c2;
                if (d2 == 0) indexsArray[1].Add(i);
                else if (d2 > 0)
                {
                    var d3 = a3 * x + b3 * y + c3;
                    if (d3 == 0) indexsArray[3].Add(i);
                    else if (d3 > 0)
                    {
                        var d4 = a4 * x + b4 * y + c4;
                        if (d4 == 0) indexsArray[2].Add(i);
                        else if (d4 < 0) indexsArray[3].Add(i);
                        else PointInArea(i, indexsArray, x, y, leftY, rightY, topX, bottomX);
                    }
                    else indexsArray[1].Add(i);
                }
                else indexsArray[0].Add(i);
            }
            else//在中心点到左侧点的线的左侧
            {
                var d4 = a4 * x + b4 * y + c4;
                if (d4 >= 0) indexsArray[2].Add(i);
                else
                {
                    var d3 = a3 * x + b3 * y + c3;
                    if (d3 >= 0) indexsArray[3].Add(i);
                    else
                    {
                        var d2 = a2 * x + b2 * y + c2;
                        if (d2 >= 0) indexsArray[1].Add(i);
                        else PointInArea(i, indexsArray, x, y, leftY, rightY, topX, bottomX);
                    }
                }
            }
        }

        private void PointInArea(int i, ConcurrentBag<int>[] indexsArray, double x, double y, double leftY, double rightY, double topX, double bottomX)
        {
            if (x < topX && y > leftY) indexsArray[0].Add(i);
            else if (x > topX && y > rightY) indexsArray[1].Add(i);
            else if (x < bottomX && y < leftY) indexsArray[2].Add(i);
            else if (x > bottomX && y < rightY) indexsArray[3].Add(i);
        }

        public override BaseGraphicalModel GetGraphicalMode()
        {
            if (Points == null) return null;
            return new QuadrantGraphicalModel()
            {
                AreaNames = Areas.Select(p => p.Name).ToArray(),
                AreaColors = this.Areas.Select(p => p.DisplayColor).ToArray(),
                Left = Points[0],
                Top = Points[1],
                Right = Points[2],
                Bottom = Points[3],
                CenterX = Points[4],
                CenterY = Points[5]
            };
        }
    }

    public class QuadrantGraphicalModel : BaseGraphicalModel
    {
        public double Left { get; set; }
        public double Top { get; set; }
        public double Right { get; set; }
        public double Bottom { get; set; }
        public double CenterX { get; set; }
        public double CenterY { get; set; }
    }
}
